package beanstao.util.store.database;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;

import beanstao.util.Util;
import beanstao.util.collections.interfaces.IEach;
import beanstao.util.enums.operator.Comparison;
import beanstao.util.exception.technical.util.ExitLoopException;
import beanstao.util.exception.technical.util.LoopException;
import beanstao.util.store.database.interfaces.IExpGroup;
import beanstao.util.store.database.interfaces.IExpression;
import beanstao.util.store.database.interfaces.IGroupBy;
import beanstao.util.store.database.interfaces.IOrderBy;


/**
 * Where 是 ICondition 的一个实现，事实上他是一个SQL语句的条件部份<br>
 * 这个类提供比较通用的方法来构建 ICondition 接口的实例。 比如一个通常的查询:
 * <p>
 * List<Pet> pets = dao.query(Pet.class, Where.where("name","LIKE","B%").asc("name"), null);<br>
 * Where.where("id", ">", 34).and("name","LIKE","T%").asc("name"); <br>
 * 相当于<br>
 * WHERE id>34 AND name LIKE 'T%' ORDER BY name ASC<br>
 * Where.orderBy().desc("id"); <br>
 * 相当于<br>
 * ORDER BY id DESC <br>
 * 设置的字段名，是 java 的字段名 -- 如果 Domain 里有，那么会被转换成数据库字段名<br>
 * 如果设置的是 Domain 中不存在的 java 字段名，则被认为是数据库字段名，将直接使用<br>
 * 如果是字符串，或者其他类字符串对象（某种 CharSequence），那么在转换成 Sql 时，会正确被单引号包裹<br>
 * 如果是不可理解的自定义对象，会被转化成字符串处理
 */
public class Where implements IExpGroup, IOrderBy, IGroupBy
{
	public static Where ME()
	{
		return new Where();
	}

	public static IExpression exp(String name, Comparison co, Object value)
	{
		return new Exp(name, co, value);
	}

	public static IExpGroup exps(String name, Comparison co, Object value)
	{
		return new Where(Where.exp(name, co, value));
	}

	public static IExpGroup exps(IExpression e)
	{
		return new Where(e);
	}

	public static Where where(String name, Comparison co, Object value)
	{
		return new Where(Where.exp(name, co, value));
	}

	public static Where where(IExpression e)
	{
		return new Where(e);
	}

	public static IOrderBy orderBy()
	{
		return new Where();
	}

	public static IGroupBy groupBy()
	{
		return new Where();
	}

	// =================================================

	Where()
	{
		exps = new LinkedList<IExpression>();
		orderBys = new LinkedList<OrderBy>();
		groupBys = new LinkedList<GroupBy>();
	}

	protected Where(IExpression e)
	{
		exps = new LinkedList<IExpression>();
		exps.add(e);
		orderBys = new LinkedList<OrderBy>();
		groupBys = new LinkedList<GroupBy>();
	}

	private final List<IExpression> exps;
	private final List<OrderBy> orderBys;
	private final List<GroupBy> groupBys;

	public IOrderBy asc(String name)
	{
		orderBys.add(new OrderBy(name, "ASC"));
		return this;
	}

	public IOrderBy desc(String name)
	{
		orderBys.add(new OrderBy(name, "DESC"));
		return this;
	}

	@Override
	public IGroupBy addGroupBy(String name)
	{
		groupBys.add(new GroupBy(name));
		return this;
	}

	private Where add(String s, IExpression e)
	{
		exps.add(new Lop(s));
		exps.add(e);
		return this;
	}

	public Where and(IExpression e)
	{
		return this.add(" AND ", e);
	}

	public Where and(String name, Comparison co, Object value)
	{
		return this.and(Where.exp(name, co, value));
	}

	public Where or(IExpression e)
	{
		return this.add(" OR ", e);
	}

	public Where or(String name, Comparison co, Object value)
	{
		return this.or(Where.exp(name, co, value));
	}

	public Where andNot(IExpression e)
	{
		return this.add(" AND NOT ", e);
	}

	public Where andNot(String name, Comparison co, Object value)
	{
		return this.andNot(Where.exp(name, co, value));
	}

	public Where orNot(IExpression e)
	{
		return this.add(" OR NOT ", e);
	}

	public Where orNot(String name, Comparison co, Object value)
	{
		return this.orNot(Where.exp(name, co, value));
	}

	public String toSql(Fields columns)
	{
		StringBuilder sb = new StringBuilder();
		if (exps != null)
		{
			boolean failFirst = false;
			for (int i = 0; i < exps.size(); i++)
			{
				IExpression expression = exps.get(i);
				if (null == expression)
				{
					failFirst = true;
					continue;
				}
				if (failFirst)
				{
					failFirst = false;
					continue;
				}
				if ((i == 0) && (expression instanceof Lop))
					continue;
				expression.render(sb, columns);
			}
		}
		if (groupBys.size() > 0)
		{
			if (sb.length() > 0)
				sb.append(' ');
			sb.append("GROUP BY");
			for (Iterator<GroupBy> it = groupBys.iterator(); it.hasNext();)
			{
				it.next().render(sb, columns);
				if (it.hasNext())
					sb.append(",");
			}
		}
		if (orderBys.size() > 0)
		{
			if (sb.length() > 0)
				sb.append(' ');
			sb.append("ORDER BY");
			for (Iterator<OrderBy> it = orderBys.iterator(); it.hasNext();)
			{
				it.next().render(sb, columns);
				if (it.hasNext())
					sb.append(",");
			}
		}
		return sb.toString().replace("  ", " ");
	}

	public void render(StringBuilder sb, Fields columns)
	{
		sb.append('(').append(this.toSql(columns)).append(')');
	}

	// ================================================

	private static class OrderBy implements IExpression
	{
		OrderBy(String name, String by)
		{
			this.name = name;
			this.by = by;
		}

		private final String name;
		private final String by;

		public void render(StringBuilder sb, Fields columns)
		{
			String colName = name;
			if (null != columns)
				colName = columns.get(name).getName();
			sb.append(' ').append(colName).append(' ').append(by);
		}
	}

	private static class GroupBy implements IExpression
	{
		GroupBy(String name)
		{
			this.name = name;
		}

		private final String name;

		public void render(StringBuilder sb, Fields columns)
		{
			String colName = name;
			if (null != columns)
				colName = columns.get(name).getName();
			sb.append(' ').append(colName);
		}
	}

	private static class Lop implements IExpression
	{
		private final String s;

		private Lop(String txt)
		{
			s = txt;
		}

		public void render(StringBuilder sb, Fields columns)
		{
			sb.append(s);
		}
	}

	private static class Exp implements IExpression
	{

		private static final Pattern ptn = Pattern.compile("IS|LIKE|IN", Pattern.CASE_INSENSITIVE);

		Exp(String name, Comparison co, Object value)
		{
			this.name = name;
			op = Comparison.toString(co);
			if (ptn.matcher(op).find())
				op = op.toUpperCase();
			this.value = value;
		}

		private final String name;
		private String op;
		private final Object value;

		public void render(final StringBuilder sb, Fields columns)
		{
			sb.append(' ');
			if (null != columns)
			{
				String colName = name;
				if (null != columns)
				{
					Field field = columns.get(name);
					if (null != field)
						colName = field.getName();
				}
				sb.append(colName);
			}
			else
				sb.append(name);
			// IN
			if ("IN".equals(op))
			{
				sb.append(" IN ");
				if (null == value)
					throw Util.makeThrow("SQL 'IN' null : %s", sb);
				// 如果是集合或者数组
				if ((value instanceof Collection<?>) || value.getClass().isArray())
				{
					sb.append('(');
					Util.each(value, new IEach<Object>()
					{
						public void invoke(int i, Object ele, int length) throws ExitLoopException, LoopException
						{
							sb.append(DbHelper.formatFieldValue(ele)).append(',');
						}
					});
					sb.setCharAt(sb.length() - 1, ')');
				}
				// 否则当作一般字符串处理
				else
					sb.append(DbHelper.formatFieldValue(value));
			}
			// IS NULL
			else if ((null == value) && "IS".equals(op))
				sb.append(" IS NULL");
			else if ("LIKE".equals(op) || "IS".equals(op))
				sb.append(' ').append(op).append(' ').append(DbHelper.formatFieldValue(value));
			else
				sb.append(op).append(DbHelper.formatFieldValue(value));
		}
	}

}
